#include "OpenGLShaderProgram.h"
#include "OpenGLUtils.h"
#include <memory>
#include <list>
#include <vector>
#include <sstream>
#include <regex>
#include "shaders/ShaderChunk.h"
using namespace std;
POCELAIN_NAMESPACE_BEGIN
OpenGLShaderProgram::OpenGLShaderProgram()
{
}
OpenGLShaderProgram::~OpenGLShaderProgram()
{
    clear();
}
OpenGLShaderProgram::OpenGLShaderProgram(OpenGLShaderProgram &&other)
{
    swap(other);
}
OpenGLShaderProgram &OpenGLShaderProgram::operator=(OpenGLShaderProgram &&other)
{
    clear();
    swap(other);
    return *this;
}
void OpenGLShaderProgram::swap(OpenGLShaderProgram &other)
{
    GLuint id = mProgramId;
    mProgramId = other.mProgramId;
    other.mProgramId = id;
}
void OpenGLShaderProgram::clear()
{
    if(mProgramId!=0)
    {
        glDeleteProgram(mProgramId);
        POCELAIN_OPENGL_CHECK_ERROR("glDelteProgram");
        mProgramId = 0;
    }
}
bool OpenGLShaderProgram::isValid()const
{
    if(mProgramId!=0)
    {
        return glIsProgram(mProgramId) == GL_TRUE;
        POCELAIN_OPENGL_CHECK_ERROR("glIsProgram");
    }
    return false;
}

bool OpenGLShaderProgram::linkProgram(
    OpenGLShader *vert,
    OpenGLShader *frag,
    const defines_t &defines,
    const std::map<std::string,EUniformType> &attributeTypeMap,
    const std::map<std::string,EUniformType> &uniformMap)
{
    return linkProgram(nullptr,nullptr,vert,frag,defines,attributeTypeMap,uniformMap);
}

bool OpenGLShaderProgram::linkProgram(
    OpenGLShader *tess,
    OpenGLShader *geom,
    OpenGLShader *vert,
    OpenGLShader *frag,
    const defines_t &defines,
    const std::map<std::string,EUniformType> &attributeTypeMap,
    const std::map<std::string,EUniformType> &uniformMap)
{
    POCELAIN_ASSERT(vert&&frag);
    clear();

    bool ret = true;
    if(vert->mIsDirty)
    {
        if(!compileShader(vert,defines,attributeTypeMap,uniformMap))
        {
            POCELAIN_LOG_ERROR("Vertex shader compile failed");
            return false;
        }
    }
    if(frag->mIsDirty)
    {
        if(!compileShader(frag,defines,attributeTypeMap,uniformMap))
        {
            POCELAIN_LOG_ERROR("Fragment shader compile failed");
            return false;
        }
    }
    if(geom&&geom->mIsDirty)
    {
        if(!compileShader(geom,defines,attributeTypeMap,uniformMap))
        {
            POCELAIN_LOG_ERROR("Geometry shader compile failed");
            return false;
        }
    }
    if(tess&&tess->mIsDirty)
    {
        if(!compileShader(tess,defines,attributeTypeMap,uniformMap))
        {
            POCELAIN_LOG_ERROR("Tesselation shader compile failed");
            return false;
        }
    }

    GLuint programId = glCreateProgram();
    POCELAIN_OPENGL_CHECK_ERROR("glCreateProgram");
    glAttachShader(programId, vert->getShaderID());
    POCELAIN_OPENGL_CHECK_ERROR("glAttachShader");
    glAttachShader(programId, frag->getShaderID());
    POCELAIN_OPENGL_CHECK_ERROR("glAttachShader");
    if(tess!=nullptr)
    {
        glAttachShader(programId, tess->getShaderID());
        POCELAIN_OPENGL_CHECK_ERROR("glAttachShader");
    }
    if(geom!=nullptr)
    {
        glAttachShader(programId, geom->getShaderID());
        POCELAIN_OPENGL_CHECK_ERROR("glAttachShader");
    }
    glLinkProgram(programId);
    POCELAIN_OPENGL_CHECK_ERROR("glLinkProgram");

    GLint result = GL_FALSE;
    glGetProgramiv(programId,GL_LINK_STATUS,&result);
    if(result!=GL_TRUE)//编译出错
    {
        int infoLogLength;
        glGetProgramiv(programId,GL_INFO_LOG_LENGTH,&infoLogLength);
        if(infoLogLength>0)
        {
            char *infoLogBuf = new char[infoLogLength+1];
            unique_ptr<char[]> u(infoLogBuf);
            infoLogBuf[infoLogLength] = '\0';
            glGetProgramInfoLog(programId,infoLogLength,NULL,infoLogBuf);
            POCELAIN_LOG_ERROR("Program info : %s\n",infoLogBuf);
            return false;
        }
    }
    POCELAIN_OPENGL_CHECK_ERROR("glGetProgramiv");

    mProgramId = programId;
    //取消shader附属
    glDetachShader(programId,vert->getShaderID());
    glDetachShader(programId,frag->getShaderID());
    if(tess!=nullptr)
    {
        glDetachShader(programId,tess->getShaderID());
    }
    if(geom!=nullptr)
    {
        glDetachShader(programId,geom->getShaderID());
    }
    POCELAIN_OPENGL_CHECK_ERROR("glDetachShader");
    return true;
}
void OpenGLShaderProgram::use()
{
    if(isValid())
    {
        glUseProgram(mProgramId);
        POCELAIN_OPENGL_CHECK_ERROR("glUseProgram");
    }
}
GLint OpenGLShaderProgram::getAttributeLocation(const std::string &attrName)
{
    POCELAIN_LOG_DEBUG("getAttributeLocation : %s\n",attrName.c_str());
    auto ret = glGetAttribLocation(mProgramId,attrName.c_str());
    POCELAIN_OPENGL_CHECK_ERROR("glGetAttribLocation");
    return ret;
}

GLint OpenGLShaderProgram::getUniform(const std::string &uniName)
{
    auto ret = glGetUniformLocation(mProgramId,uniName.c_str());
    POCELAIN_OPENGL_CHECK_ERROR("glGetUniformLocation");
    return ret;
}
void OpenGLShaderProgram::enableVertexAttributeArray(const std::string &attrName)
{
    enableVertexAttributeArray(getAttributeLocation(attrName));
}
void OpenGLShaderProgram::enableVertexAttributeArray(GLint location)
{
    glEnableVertexAttribArray(location);
    POCELAIN_OPENGL_CHECK_ERROR("glEnableVertexAttribArray");
}
void OpenGLShaderProgram::disableVertexAttributeArray(const std::string &attrName)
{
    disableVertexAttributeArray(getAttributeLocation(attrName));
}
void OpenGLShaderProgram::disableVertexAttributeArray(GLint location)
{
    glDisableVertexAttribArray(location);
    POCELAIN_OPENGL_CHECK_ERROR("glDisableVertexAttribArray");
}
static std::vector<std::string> VERTEX_SHADER_PREFIX_DEFINE = {
//    "precision mediump sampler2DArray;",
    "#define attribute in",
    "#define varying out",
    "#define texture2D texture"
};
static std::vector<std::string> FRAGMENT_SHADER_PREFIX_DEFINE = {
    "#define varying in",
//    ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;',
//    ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor',
    "#define gl_FragDepthEXT gl_FragDepth",
    "#define texture2D texture",
    "#define textureCube texture",
    "#define texture2DProj textureProj",
    "#define texture2DLodEXT textureLod",
    "#define texture2DProjLodEXT textureProjLod",
    "#define textureCubeLodEXT textureLod",
    "#define texture2DGradEXT textureGrad",
    "#define texture2DProjGradEXT textureProjGrad",
    "#define textureCubeGradEXT textureGrad"
};

static std::string uniformType2ShaderDecType(EUniformType type)
{
    static std::vector<std::string> type2str;
    if(type2str.size()==0)
    {
        type2str.resize(size_t(E_UNIFORM_END));

        //boolean
        type2str[E_UNIFORM_BOOL] = "bool";

        // integer
        type2str[E_UNIFORM_BYTE] = "byte";
        type2str[E_UNIFORM_UBYTE] = "ubyte";
        type2str[E_UNIFORM_SHORT] = "short";
        type2str[E_UNIFORM_USHORT] = "ushort";
        type2str[E_UNIFORM_INT] = "int";
        type2str[E_UNIFORM_UINT] = "uint";

        // float
        //E_UNIFORM_HALF_FLOAT,
        type2str[E_UNIFORM_FLOAT] = "float";
        type2str[E_UNIFORM_DOUBLE] = "double";

        //vector
        type2str[E_UNIFORM_VECTOR2] = "vec2";
        type2str[E_UNIFORM_VECTOR3] = "vec3";
        type2str[E_UNIFORM_VECTOR4] = "vec4";

        type2str[E_UNIFORM_DVECTOR2] = "dvec2";
        type2str[E_UNIFORM_DVECTOR3] = "dvec3";
        type2str[E_UNIFORM_DVECTOR4] = "dvec4";

        type2str[E_UNIFORM_BVECTOR2] = "bvec2";
        type2str[E_UNIFORM_BVECTOR3] = "bvec3";
        type2str[E_UNIFORM_BVECTOR4] = "bvec4";

        type2str[E_UNIFORM_IVECTOR2] = "ivec2";
        type2str[E_UNIFORM_IVECTOR3] = "ivec3";
        type2str[E_UNIFORM_IVECTOR4] = "ivec4";

        type2str[E_UNIFORM_UVECTOR2] = "uvec2";
        type2str[E_UNIFORM_UVECTOR3] = "uvec3";
        type2str[E_UNIFORM_UVECTOR4] = "uvec4";

        //matrix
        type2str[E_UNIFORM_MATRIX2x2] = "mat2";
        type2str[E_UNIFORM_MATRIX2x3] = "mat2x3";
        type2str[E_UNIFORM_MATRIX2x4] = "mat2x4";

        type2str[E_UNIFORM_MATRIX3x2] = "mat3x2";
        type2str[E_UNIFORM_MATRIX3x3] = "mat3";
        type2str[E_UNIFORM_MATRIX3x4] = "mat3x4";

        type2str[E_UNIFORM_MATRIX4x2] = "mat4x2";
        type2str[E_UNIFORM_MATRIX4x3] = "mat4x3";
        type2str[E_UNIFORM_MATRIX4x4] = "mat4";

        type2str[E_UNIFORM_DMATRIX2x2] = "dmat2";
        type2str[E_UNIFORM_DMATRIX2x3] = "dmat2x3";
        type2str[E_UNIFORM_DMATRIX2x4] = "dmat2x4";

        type2str[E_UNIFORM_DMATRIX3x2] = "dmat3x2";
        type2str[E_UNIFORM_DMATRIX3x3] = "dmat3";
        type2str[E_UNIFORM_DMATRIX3x4] = "dmat3x4";

        type2str[E_UNIFORM_DMATRIX4x2] = "dmat4x2";
        type2str[E_UNIFORM_DMATRIX4x3] = "dmat4x3";
        type2str[E_UNIFORM_DMATRIX4x4] = "dmat4";

        //sampler
        type2str[E_UNIFORM_SAMPLER_1D] = "sampler";
        type2str[E_UNIFORM_SAMPLER_2D] = "sampler2D";
        type2str[E_UNIFORM_SAMPLER_3D] = "sampler3D";
    }
    return type2str[type];
}
bool OpenGLShaderProgram::compileShader(
    OpenGLShader *shader,
    const defines_t &defines,
    const std::map<std::string,EUniformType> &attributeTypeMap,
    const std::map<std::string,EUniformType> &uniformMap
    )
{
    if(!shader->mIsDirty)
        return true;

    auto shaderType = shader->getShaderType();
    // it is no need to parse raw shader
    if(shader->mIsRawShader)
    {
        shader->mRawShaderString = shader->mShaderString;
    }
    else
    {

        const std::string &content = shader->getShaderString();
        std::list<std::string> shaderLines;
        std::stringstream iss(content);
        std::string sRegInclude = "^[ \\t]*#include *<\\s*([\\w\\d./]+)\\s*>";
        // replace gl_FragColor , confirm not like  "abcgl_fragColordef"
        std::string sRegFragColor = "([~0-9a-zA-Z_]?)(gl_FragColor)([~0-9a-zA-Z_]?)";
        std::regex regInclude(sRegInclude);
        std::regex regFragColor(sRegFragColor);
        std::smatch matchResult;

        shaderLines.push_back("#version 330");
        if(shaderType==EShaderType::E_VERTEX)
        {
            shaderLines.insert(shaderLines.end(),VERTEX_SHADER_PREFIX_DEFINE.cbegin(),VERTEX_SHADER_PREFIX_DEFINE.cend());
        }
        else if(shaderType==EShaderType::E_FRAGMENT)
        {
            shaderLines.insert(shaderLines.end(),FRAGMENT_SHADER_PREFIX_DEFINE.cbegin(),FRAGMENT_SHADER_PREFIX_DEFINE.cend());
            // add out vec4 out_FragColor to first line
            // because of OpenGL 4 not support gl_FragColor
            // and not permit to declare gl_ prefix variable
            shaderLines.push_back("out vec4 out_FragColor;");
        }
        for(auto &p : defines)
        {
            std::string define = "#define ";
            define += p.first + ' ' + p.second;
            shaderLines.push_back(define);
        }

        if(shader->getShaderType()==EShaderType::E_VERTEX)
        {
            for(auto &a : attributeTypeMap)
            {
                const std::string &attName = a.first;
                auto aType = a.second;
                std::string typeStr = uniformType2ShaderDecType(aType);
                std::string attLine = std::string("attribute ") + typeStr + " " + attName + ";";
                shaderLines.push_back(attLine);
            }
        }

        for(auto &u : uniformMap)
        {
            const std::string &name = u.first;
            auto type = u.second;
            std::string typeStr = uniformType2ShaderDecType(type);
            std::string line = std::string("uniform ") + typeStr + " " + name + ";";
            shaderLines.push_back(line);
        }

        auto &shaderChunk = ShaderChunk::getShaderChunk();
        for(std::string line ; std::getline(iss,line);)
        {
            if(std::regex_search(line,matchResult,regInclude))
            {
                //            POCELAIN_LOG_INFO("matchline :",line.c_str());
                //            for(size_t i = 0 ; i < matchResult.size() ; ++ i)
                //            {
                //                POCELAIN_LOG_INFO("matchResult [%ld] : %s\n",i, matchResult[i].str().c_str());
                //            }
                if(matchResult.size()==2)
                {
                    std::string fileName = matchResult[1];
                    auto itChunk = shaderChunk.find(fileName);
                    if(itChunk != shaderChunk.end())
                    {
                        line  = itChunk->second;
                    }
                    else
                    {
                        POCELAIN_LOG_ERROR("ShaderChunk not exists shader chunk name : %s" ,fileName.c_str());
                        assert(false);
                    }
                }
            }
            line = regex_replace(line,regFragColor,"$1out_FragColor$3");
            shaderLines.push_back(line);
        }

        std::stringstream oss;
        for(auto &s : shaderLines)
        {
            oss<< s << '\n';
        }

        shader->mRawShaderString = oss.str();
    }

    GLuint sId;
    switch(shaderType)
    {
    case EShaderType::E_VERTEX:
        {
            sId = glCreateShader(GL_VERTEX_SHADER);
            break;
        }
    case EShaderType::E_FRAGMENT:
        {
            sId = glCreateShader(GL_FRAGMENT_SHADER);
            break;
        }
    case EShaderType::E_GEOMETRY:
        {
            sId = glCreateShader(GL_GEOMETRY_SHADER);
            break;
        }
    case EShaderType::E_TESSELATION:
#ifdef GL_TESSELATION_SHADER
        {
            sId = glCreateShader(GL_TESSELATION_SHADER);
            break;
        }
#else
        {
            POCELAIN_LOG_ERROR("Error Not Support tesselation shader");
            return false;
            break;
        }
#endif
    case EShaderType::E_COMPUTE:
        {
            sId = glCreateShader(GL_COMPUTE_SHADER);
            break;
        }
    default:
        {
            POCELAIN_LOG_ERROR("Error Shder Type Unknown");
            return false;
            break;
        }
    }

    //编译shader
    const char *shaderPointer = shader->mRawShaderString.c_str();
    POCELAIN_LOG_INFO("shaderSource : \n%s\n",OpenGLUtils::shaderSourceFormat(shaderPointer).c_str());
    glShaderSource(sId, 1, &shaderPointer, NULL);
    POCELAIN_OPENGL_CHECK_ERROR("glShaderSource");
    glCompileShader(sId);
    POCELAIN_OPENGL_CHECK_ERROR("glCompileShader");

    //check result
    GLint result = GL_FALSE;
    glGetShaderiv(sId,GL_COMPILE_STATUS,&result);
    if(result!=GL_TRUE) //compile error ,log it out
    {
        int infoLogLength;
        glGetShaderiv(sId,GL_INFO_LOG_LENGTH,&infoLogLength);
        POCELAIN_OPENGL_CHECK_ERROR("glGetShaderiv");
        if(infoLogLength>0)
        {
            char *infoLogBuf = new char[infoLogLength+1];
            unique_ptr<char[]> u(infoLogBuf);
            infoLogBuf[infoLogLength] = '\0';
            glGetShaderInfoLog(sId,infoLogLength,NULL,infoLogBuf);
            POCELAIN_OPENGL_CHECK_ERROR("glGetShaderInfoLog");
            POCELAIN_LOG_ERROR("Shader compile error : %s",infoLogBuf);
            POCELAIN_LOG_ERROR("%s\n",OpenGLUtils::shaderSourceFormat(shader->mRawShaderString).c_str());
            abort();
        }
        // TODO : delete shader whatever
        glDeleteShader(sId);
        POCELAIN_OPENGL_CHECK_ERROR("glDeleteShader");
        return false;
    }
    shader->mShaderId = sId;
    shader->mIsDirty = false;
    return true;
}
POCELAIN_NAMESPACE_END
